﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace Gunningine
{
    [StructLayout(LayoutKind.Sequential)]
    public struct Vector2
    {
        public float X;

        public float Y;

        /// <summary>
        /// Length of vector.
        /// </summary>
        public float Length
        {
            get
            {
                return (float)Math.Sqrt(this.SquaredLength);
            }
        }

        /// <summary>
        /// Squared length, useful for when you just need to compare two lengths.
        /// </summary>
        public float SquaredLength
        {
            get
            {
                return this.X * this.X + this.Y * this.Y;
            }
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="x">x value</param>
        /// <param name="y">y value</param>
        public Vector2(float x, float y)
        {
            this.X = x;
            this.Y = y;
        }

        /// <summary>
        /// Copy constructor.
        /// </summary>
        /// <param name="that">other vector.</param>
        public Vector2(Vector2 that)
        {
            this.X = that.X;
            this.Y = that.Y;
        }

        /// <summary>
        /// Gets or sets the value at the index of the Vector.
        /// </summary>
        public float this[int index]
        {
            get
            {
                if (index == 0) return this.X;
                else if (index == 1) return this.Y;
                throw new IndexOutOfRangeException("You tried to access this vector at index: " + index);
            }
            set
            {
                if (index == 0) this.X = value;
                else if (index == 1) this.Y = value;
                else throw new IndexOutOfRangeException("You tried to set this vector at index: " + index);
            }
        }

        /// <summary>
        /// Unary minus operator.
        /// </summary>
        /// <param name="a">vector.</param>
        /// <returns>vector.</returns>
        public static Vector2 operator -(Vector2 a)
        {
            return new Vector2(-a.X, -a.Y);
        }

        /// <summary>
        /// Addition operator.
        /// </summary>
        /// <param name="a">vector.</param>
        /// <param name="b">vector.</param>
        /// <returns>vector.</returns>
        public static Vector2 operator +(Vector2 a, Vector2 b)
        {
            return new Vector2(a.X + b.X, a.Y + b.Y);
        }

        /// <summary>
        /// Subtraction operator.
        /// </summary>
        /// <param name="a">vector.</param>
        /// <param name="b">vector.</param>
        /// <returns>vector.</returns>
        public static Vector2 operator -(Vector2 a, Vector2 b)
        {
            return new Vector2(a.X - b.X, a.Y - b.Y);
        }

        /// <summary>
        /// Multiplication operator.
        /// </summary>
        /// <param name="a">vector.</param>
        /// <param name="b">scalar.</param>
        /// <returns>vector.</returns>
        public static Vector2 operator *(Vector2 a, float scalar)
        {
            return new Vector2(a.X * scalar, a.Y * scalar);
        }

        /// <summary>
        /// Division operator.
        /// </summary>
        /// <param name="a">vector.</param>
        /// <param name="b">scalar.</param>
        /// <returns>vector.</returns>
        public static Vector2 operator /(Vector2 a, float scalar)
        {
            float recip = 1.0f / scalar;
            return new Vector2(a.X * recip, a.Y * recip);
        }

        /// <summary>
        /// Normalise this vector.
        /// </summary>
        public void Normalise()
        {
            double len = this.Length;
            if (len > 1e-08)
            {
                float invLen = (float)(1.0 / len);
                this.X *= invLen;
                this.Y *= invLen;
            }
        }

        /// <summary>
        /// Normalise a copy of this vector and return it.
        /// </summary>
        /// <returns>a copy of this vector, normalised.</returns>
        public Vector2 Normalised()
        {
            Vector2 vec = new Vector2(this.X, this.Y);
            vec.Normalise();
            return vec;
        }

        /// <summary>
        /// Absolute distance between two vectors.
        /// </summary>
        /// <param name="vec">vector.</param>
        /// <returns>distance between them.</returns>
        public float Distance(Vector2 vec)
        {
            return (this - vec).Length;
        }

        /// <summary>
        /// Interpolate to target position
        /// </summary>
        /// <param name="to">target position</param>
        /// <param name="distance">distance in [0, 1].</param>
        /// <returns></returns>
        public Vector2 Lerp(Vector2 to, float distance)
        {
            return new Vector2(this.X + (to.X - this.X) * distance, this.Y + (to.Y - this.Y) * distance);
        }

        /// <summary>
        /// Absolute (squared) distance between two vectors.
        /// Useful for example, when you want to find the closest
        /// point to another, but don't care about the actual distance.
        /// </summary>
        /// <param name="vec">vector.</param>
        /// <returns>distance between them.</returns>
        public float SquaredDistance(Vector2 vec)
        {
            return (this - vec).SquaredLength;
        }

        /// <summary>
        /// GetHashCode.
        /// </summary>
        /// <returns></returns>
        public override int GetHashCode()
        {
            return this.X.GetHashCode() ^ this.Y.GetHashCode();
        }

        public static readonly Vector2 UnitX = new Vector2(1, 0);

        public static readonly Vector2 UnitY = new Vector2(0, 1);

        public static readonly Vector2 NegativeUnitX = new Vector2(-1, 0);

        public static readonly Vector2 NegativeUnitY = new Vector2(0, -1);

        public static readonly Vector2 Zero = new Vector2(0, 0);

        public static readonly Vector2 One = new Vector2(1, 1);
    }
}
